﻿using System.Collections.Generic;
using System.Linq;
using Behaviorize.CodeGenerator.CodeModel;
using EnvDTE;
using EnvDTE80;

namespace Behaviorize.CodeGenerator.CodeGenerating
{
	internal static class TestCodeGenerator
	{
		internal static void CreateOrUpdateCode(Feature feature, CodeClass2 testClass)
		{
			foreach (Scenario scenario in feature.Scenarios)
			{
				CreateOrUpdateStepMethods(scenario, testClass);
			}
		}

		private class StepMethod
		{
			public StepDefinition Step { get; set; }
			public string Body { get; set; }

			public StepMethod(StepDefinition step, string body)
			{
				Step = step;
				Body = body;
			}
		}

		private static void CreateOrUpdateStepMethods(Scenario scenario, CodeClass testClass)
		{
			var methodsToCreate = new List<StepMethod>();

			foreach (StepDefinition step in scenario.Given.Concat(scenario.When).Concat(scenario.Then))
			{
				CodeFunction stepMethod = GetStepMethod(step, testClass);

				if (stepMethod == null)
				{
					methodsToCreate.Add(new StepMethod(step, null));
				}
				else if (!step.ParametersMatch(stepMethod.Parameters))
				{
					var startPoint = stepMethod.GetStartPoint(vsCMPart.vsCMPartBody);
					var endPoint = stepMethod.GetEndPoint(vsCMPart.vsCMPartBody);
					string methodBody = startPoint.CreateEditPoint().GetText(endPoint).Trim('\r', '\n');

					testClass.RemoveMember(stepMethod);
					methodsToCreate.Add(new StepMethod(step, methodBody));
				}
			}

			CreateStepMethods(testClass, methodsToCreate);
		}

		private static CodeFunction GetStepMethod(StepDefinition step, CodeClass testClass)
		{
			return testClass.Children.OfType<CodeFunction>().SingleOrDefault(f => f.Name == step.GetMethodName());
		}

		private static void CreateStepMethods(CodeClass testClass, List<StepMethod> stepsWithoutMethods)
		{
			var uniqueStepMethods = (from stepMethod in stepsWithoutMethods
			                         group stepMethod by stepMethod.Step.GetMethodSignatureHashCode()
			                         into stepGroup
			                         select
			                         	stepGroup.Count() == 1
			                         		? new
			                         			{
			                         				Name = stepGroup.Single().Step.GetMethodName(),
			                         				Parameters = stepGroup.Single().Step.GetMethodParameters(false),
			                         				stepGroup.Single().Body
			                         			}
			                         		: stepGroup.All(s => s.Step.GetMethodSignatureHashCode() == stepGroup.First().Step.GetMethodSignatureHashCode())
			                         		  	? new
			                         		  		{
			                         		  			Name = stepGroup.First().Step.GetMethodName(),
			                         		  			Parameters = stepGroup.First().Step.GetMethodParameters(false),
			                         		  			stepGroup.First().Body
			                         		  		}
			                         		  	: new
			                         		  		{
			                         		  			Name = stepGroup.Single().Step.GetMethodName(),
			                         		  			Parameters = stepGroup.First().Step.GetMethodParameters(true),
			                         		  			stepGroup.First().Body
			                         		  		}).ToList();

			foreach (var step in uniqueStepMethods)
			{
				CodeFunction stepMethod = testClass.AddFunction(step.Name,
				                                                vsCMFunction.vsCMFunctionFunction,
				                                                vsCMTypeRef.vsCMTypeRefVoid,
				                                                Access: vsCMAccess.vsCMAccessPrivate,
																Position: -1);

				foreach (var parameter in step.Parameters)
				{
					stepMethod.AddParameter(parameter.Name, parameter.Type, -1);
				}

				if (step.Body != null)
				{
					stepMethod.GetStartPoint(vsCMPart.vsCMPartBody).CreateEditPoint().Insert(step.Body);
				}
				else
				{
					stepMethod.GetStartPoint(vsCMPart.vsCMPartBody).CreateEditPoint().Insert(new string('\t', 3) + "throw new NotImplementedException();");
				}
			}
		}
	}
}